Skip to content

Conversation

@arepala-uml
Copy link
Contributor

@arepala-uml arepala-uml commented Aug 25, 2025

  1. Added TransactionMetadatum type to properly encode and decode transaction metadata as defined in the CDDL.
  2. TransactionMetadatum replaces the use of cbor.LazyValue across each era transaction and supports decoding of int, bytes, text, lists, and maps.

Closes #1136

Summary by CodeRabbit

  • New Features

    • Added a structured, type-safe transaction metadata model with CBOR (un)marshal support and helpers for various key types.
  • Refactor

    • Multiple ledger eras updated to use the new metadata representation and to embed metadata differently during serialization.
  • Tests

    • Added metadata round-trip tests; minor test comment cleanup.

✏️ Tip: You can customize this high-level summary in your review settings.

@arepala-uml arepala-uml marked this pull request as ready for review August 25, 2025 18:47
@arepala-uml arepala-uml requested a review from a team as a code owner August 25, 2025 18:47
Copy link
Contributor

@agaffney agaffney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need something like a TransactionMetadatumWrapper type, which can be used in a Transaction type and call the decode functions, similar to what you're doing in TransactionMetadataSet.

Type() int
Cbor() []byte
Metadata() *cbor.LazyValue
Metadata() TransactionMetadataSet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to use TransactionMetadataSet here, only TransactionMetadatum. The Set is used at the block level to return the metadatum for all TXs included in the block.

type TransactionMetadataSet map[uint64]TransactionMetadatum

type TransactionMetadatum interface {
TypeName() string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a isTransactionMetadatum() function here, so that other types can't accidentally satisfy this interface.

TransactionBodies []ConwayTransactionBody
TransactionWitnessSets []ConwayTransactionWitnessSet
TransactionMetadataSet map[uint]*cbor.LazyValue
TransactionMetadataSet map[uint]common.TransactionMetadataSet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be TransactionMetadataSet TransactionMetadataSet. What you've got here will give you nested maps, which isn't what we want

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean TransactionMetadataSet.TransactionMetadataSet?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, I meant TransactionMetadataSet common.TransactionMetadataSet

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 24, 2025

Warning

Rate limit exceeded

@arepala-uml has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 18 minutes and 20 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 482298b and 1c3165d.

📒 Files selected for processing (1)
  • ledger/common/metadata.go (1 hunks)
📝 Walkthrough

Walkthrough

This PR replaces uses of raw CBOR lazy values for transaction metadata with a typed metadata model in ledger/common. It introduces TransactionMetadatum types (MetaInt, MetaBytes, MetaText, MetaList, MetaPair, MetaMap), TransactionMetadataSet with UnmarshalCBOR/MarshalCBOR, and DecodeMetadatumRaw. The Transaction interface now returns TransactionMetadatum and all era implementations (Byron, Shelley, Mary, Allegra, Alonzo, Babbage, Conway) were updated to use the new types and to embed metadata values directly in CBOR output. Adds metadata round-trip tests and minor test adjustment in Allegra.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Inspect ledger/common/metadata.go for correctness of recursive decode/encode, key-type handling, and error paths.
  • Verify Transaction interface change in ledger/common/tx.go and consistent propagation across era packages.
  • Confirm era Cbor() methods (byron, shelley, mary, allegra, alonzo, babbage, conway) embed TransactionMetadatum correctly and preserve wire-format semantics.
  • Review new test ledger/common/metadata_test.go for coverage and validity; check the relaxed assertion in ledger/allegra/block_test.go.

Files/areas to focus on:

  • ledger/common/metadata.go
  • ledger/common/metadata_test.go
  • ledger/common/tx.go
  • ledger/byron/byron.go
  • ledger/shelley/shelley.go
  • ledger/mary/mary.go
  • ledger/allegra/allegra.go and ledger/allegra/block_test.go
  • ledger/alonzo/alonzo.go
  • ledger/babbage/babbage.go
  • ledger/conway/conway.go

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Support proper encoding/decoding of TX metadata' accurately summarizes the main change in the changeset, which implements proper encoding/decoding of transaction metadata across all ledger eras.
Linked Issues check ✅ Passed All primary coding objectives from issue #1136 are met: TransactionMetadatum type created [1136], recursive encoding/decoding implemented [1136], cbor.LazyValue replaced in all Transaction types across Allegra/Alonzo/Babbage/Byron/Conway/Mary/Shelley eras [1136], and decoding tests added [1136].
Out of Scope Changes check ✅ Passed All changes directly support the linked objective of implementing proper TX metadata encoding/decoding; no unrelated modifications detected.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
ledger/babbage/babbage.go (2)

353-359: Bug: address of range variable yields duplicate pointers

Using &input appends the address of the loop variable, so all entries point to the same memory (the last element). Return the value, not its address.

 func (b *BabbageTransactionBody) ReferenceInputs() []common.TransactionInput {
   ret := []common.TransactionInput{}
   for _, input := range b.TxReferenceInputs.Items() {
-    ret = append(ret, &input)
+    ret = append(ret, input)
   }
   return ret
 }

593-599: Bug: leaking zero hash when datum is absent

Returning a pointer to an empty Blake2b256 causes RPC encoding to emit 32 zero bytes instead of “no datum”. Return nil when absent.

 func (o BabbageTransactionOutput) DatumHash() *common.Blake2b256 {
-  if o.DatumOption != nil {
-    return o.DatumOption.hash
-  }
-  return &common.Blake2b256{}
+  if o.DatumOption != nil && o.DatumOption.hash != nil {
+    return o.DatumOption.hash
+  }
+  return nil
 }
ledger/conway/conway.go (1)

468-474: Bug: address of range variable yields duplicate pointers

Same issue as Babbage: &input captures the loop variable address; all entries will be the same. Append the value.

 func (b *ConwayTransactionBody) ReferenceInputs() []common.TransactionInput {
   ret := []common.TransactionInput{}
   for _, input := range b.TxReferenceInputs.Items() {
-    ret = append(ret, &input)
+    ret = append(ret, input)
   }
   return ret
 }
♻️ Duplicate comments (2)
ledger/allegra/block_test.go (1)

63-76: Reinstate exact CBOR byte-for-byte round‑trip check

We should keep the strict equality check in addition to the structural assertions, as previously requested. If it fails, that indicates an encoding divergence we must fix rather than relaxing the test.

Apply this diff right after encoding:

@@
   if encoded == nil || len(encoded) == 0 {
     t.Fatal("Custom encoded CBOR from AllegraBlock is nil or empty")
   }
 
- // Ensure the re-encoded CBOR is structurally valid and decodes back
+ // Exact byte-for-byte match with original CBOR
+ assert.Equal(t, dataBytes, encoded, "Re-encoded CBOR must match original CBOR")
+
+ // Ensure the re-encoded CBOR is structurally valid and decodes back
   var redecoded allegra.AllegraBlock
   if err := redecoded.UnmarshalCBOR(encoded); err != nil {
     t.Fatalf("Re-encoded AllegraBlock failed to decode: %v", err)
   }
ledger/common/tx.go (1)

34-35: Seal the TransactionMetadatum interface.

Add an unexported marker (e.g., isTransactionMetadatum()) so unrelated types can’t accidentally satisfy the interface. This was requested previously and is still applicable.

Apply this diff:

 type TransactionMetadatum interface {
-   TypeName() string
+   isTransactionMetadatum()
+   TypeName() string
 }
 
 type MetaInt struct {
   Value int64
 }
+func (MetaInt) isTransactionMetadatum() {}
 
 type MetaBytes struct {
   Value []byte
 }
+func (MetaBytes) isTransactionMetadatum() {}
 
 type MetaText struct {
   Value string
 }
+func (MetaText) isTransactionMetadatum() {}
 
 type MetaList struct {
   Items []TransactionMetadatum
 }
+func (MetaList) isTransactionMetadatum() {}
 
 type MetaPair struct {
   Key   TransactionMetadatum
   Value TransactionMetadatum
 }
 
 type MetaMap struct {
   Pairs []MetaPair
 }
+func (MetaMap) isTransactionMetadatum() {}
🧹 Nitpick comments (4)
ledger/conway/conway.go (1)

58-60: Metadata model update acknowledged; ensure CBOR optional field encoding

The switch to common.TransactionMetadataSet and appending the value directly is good. As with other eras, please verify whether the 4th element should be omitted when metadata is absent instead of appending nil, to preserve canonical CBOR and hashes.

@@ func (t *ConwayTransaction) Cbor() []byte {
- if t.TxMetadata != nil {
-   tmpObj = append(tmpObj, t.TxMetadata)
- } else {
-   tmpObj = append(tmpObj, nil)
- }
+ if t.TxMetadata != nil {
+   tmpObj = append(tmpObj, t.TxMetadata)
+ }

Also applies to: 521-522, 635-637, 701-703

ledger/shelley/shelley.go (1)

56-56: Type alignment looks good; consider standardizing key width.

Using map[uint]common.TransactionMetadataSet works, but elsewhere labels use uint64. Consider switching to map[uint64]... for consistency and to avoid platform‑dependent uint width.

ledger/allegra/allegra.go (1)

52-53: Type alignment looks good; consider standardizing key width.

map[uint]common.TransactionMetadataSet works; aligning on uint64 across packages would remove platform‐dependent width.

ledger/mary/mary.go (1)

55-56: Type alignment looks good; consider key width consistency.

As with other eras, consider map[uint64]... for cross‑platform consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8981d3f and 8f01d63.

📒 Files selected for processing (9)
  • ledger/allegra/allegra.go (4 hunks)
  • ledger/allegra/block_test.go (1 hunks)
  • ledger/alonzo/alonzo.go (4 hunks)
  • ledger/babbage/babbage.go (4 hunks)
  • ledger/byron/byron.go (2 hunks)
  • ledger/common/tx.go (3 hunks)
  • ledger/conway/conway.go (4 hunks)
  • ledger/mary/mary.go (4 hunks)
  • ledger/shelley/shelley.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
ledger/byron/byron.go (1)
ledger/common/tx.go (1)
  • TransactionMetadataSet (41-41)
ledger/conway/conway.go (2)
ledger/common/tx.go (1)
  • TransactionMetadataSet (41-41)
ledger/conway.go (1)
  • ConwayTransaction (26-26)
ledger/common/tx.go (3)
cbor/decode.go (1)
  • Decode (29-44)
cbor/cbor.go (1)
  • RawMessage (36-36)
cbor/encode.go (1)
  • Encode (27-40)
ledger/allegra/block_test.go (1)
ledger/allegra/allegra.go (2)
  • AllegraBlock (46-53)
  • AllegraBlock (66-68)
ledger/mary/mary.go (1)
ledger/common/tx.go (1)
  • TransactionMetadataSet (41-41)
ledger/shelley/shelley.go (2)
ledger/common/tx.go (1)
  • TransactionMetadataSet (41-41)
ledger/shelley.go (1)
  • ShelleyTransaction (26-26)
ledger/alonzo/alonzo.go (1)
ledger/common/tx.go (1)
  • TransactionMetadataSet (41-41)
ledger/allegra/allegra.go (2)
ledger/common/tx.go (1)
  • TransactionMetadataSet (41-41)
ledger/allegra.go (1)
  • AllegraTransaction (25-25)
ledger/babbage/babbage.go (1)
ledger/common/tx.go (1)
  • TransactionMetadataSet (41-41)
🔇 Additional comments (12)
ledger/alonzo/alonzo.go (1)

58-60: Verify canonical metadata encoding against Cardano specification

The review comment assumes "canonical transaction encoding" should omit nil metadata rather than append it. However, CBOR's canonical/deterministic rules (RFC 8949) do not mandate omitting or appending (encoding) absent/null values — that is an application- or codec-level decision.

The current implementation (appending nil) is systematic across all era files (shelley, mary, conway, babbage, alonzo, allegra at lines 714, 416, 704, 919, 770, 416 respectively), suggesting this is an intentional design choice rather than an oversight. Before refactoring, confirm that Cardano's canonical transaction encoding specification actually requires omission over nil appending. Changing the serialization format could break compatibility with existing encoded transactions if the current behavior is what the protocol expects.

ledger/babbage/babbage.go (1)

58-60: Fix CBOR encoding to omit optional metadata field instead of appending null

The current implementation at line 919 appends nil when metadata is absent, but Cardano requires omitting optional metadata fields rather than encoding them as null. This breaks canonical CBOR encoding. Apply the proposed fix:

if t.TxMetadata != nil {
    tmpObj = append(tmpObj, t.TxMetadata)
-} else {
-    tmpObj = append(tmpObj, nil)
}

Likely an incorrect or invalid review comment.

ledger/byron/byron.go (1)

145-146: Remove LazyValue concern; verify semantic mapping with Byron test data

The LazyValue semantics concern is unfounded—ByronTransaction.Attributes is directly typed as common.TransactionMetadataSet, not lazily evaluated. However, the core concern remains valid: there is no decode/round-trip test to verify that Byron transaction attributes correctly map to the TransactionMetadataSet semantics expected by the common Transaction interface. Byron's historical "attributes" field may have different constraints than Shelley-era aux metadata, and this mapping should be validated with real Byron on-chain data.

ledger/shelley/shelley.go (3)

543-544: Good: switch from LazyValue to typed metadata set.

Replacing *cbor.LazyValue with common.TransactionMetadataSet improves type safety and enables proper encode/decode.


653-655: Accessor is consistent with new interface.

Returning common.TransactionMetadataSet matches the updated common.Transaction interface.


711-713: CBOR path: appending the typed metadata is correct.

Appending t.TxMetadata relies on its MarshalCBOR() — this is the right direction and avoids double-encoding. No issues here.

ledger/allegra/allegra.go (3)

242-243: Typed metadata set adoption LGTM.


352-354: Accessor matches interface change.


413-415: CBOR encoding path is correct.

Using the value directly ensures MarshalCBOR() is honored.

ledger/mary/mary.go (3)

250-251: Typed metadata set adoption LGTM.


364-366: Accessor matches interface change.


413-415: CBOR encoding path is correct.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
ledger/alonzo/alonzo.go (1)

763-769: Metadata CBOR encoding regressed

Appending t.TxMetadata (a Meta* struct) directly into tmpObj means the CBOR encoder now serializes the Go struct itself (producing a map with a "Value" field) instead of the canonical metadata item we used to append via RawMessage. This breaks round-trips and yields wire formats that no longer match the on-chain CDDL. Please restore proper encoding by marshaling the metadatum to its raw CBOR form (e.g., via a dedicated MarshalCBOR on the metadata types or a helper that converts to the underlying CBOR primitives) before appending.

ledger/shelley/shelley.go (1)

708-713: Metadata CBOR encoding regressed

ShelleyTransaction.Cbor() now feeds the Meta* structs straight into the encoder, so the wire output becomes a struct-encoded map (with "Value" keys, etc.) rather than the intended metadata CBOR. Please marshal the metadata to its canonical CBOR representation (or wrap it as a RawMessage) before appending so encoding stays compliant.

ledger/allegra/allegra.go (1)

409-415: Metadata CBOR encoding regressed

Here too we append the metadata interface value directly, so CBOR encoding emits Go-struct semantics instead of the metadata item defined in the CDDL. Please marshal the metadatum to raw CBOR (or convert it to primitive CBOR values) before appending to keep Allegra transactions encoding correctly.

ledger/mary/mary.go (1)

409-415: Metadata CBOR encoding regressed

By passing the Meta* struct straight to cbor.Encode, we emit struct-shaped CBOR rather than the intended metadata payload. Please marshal the metadata value to its canonical CBOR (or wrap it in a RawMessage) before appending so Mary transactions remain wire-compatible.

🧹 Nitpick comments (1)
ledger/common/metadata_test.go (1)

12-32: Round-trip test validates CBOR fidelity correctly.

The test properly validates byte-identical encoding without relying on stored CBOR, consistent with the repository's design goals. Using real on-chain metadata is excellent for ensuring spec compliance.

Consider adding more test cases to cover diverse metadata structures.

The PR objectives mention "decoding tests using metadata extracted from several on-chain transactions." Consider adding test cases that cover:

  • Different metadata types (int, bytes, text, lists, maps)
  • Nested structures
  • Edge cases (empty maps, single-element lists, etc.)
  • Samples from other eras (Shelley, Mary, Alonzo, Babbage, Conway)

Example structure:

func Test_Metadata_RoundTrip_Samples(t *testing.T) {
    testCases := []struct{
        name string
        hex  string
    }{
        {"AllegraSample", allegraBlockHex},
        {"ShellySample", shelleyBlockHex},
        // ... more cases
    }
    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            // ... test logic
        })
    }
}

Optionally verify decoded structure in addition to round-trip.

While byte-identical round-trips prove encoding correctness, you could also add assertions on the decoded structure to document expected metadata shapes:

// After decode
if len(set) == 0 {
    t.Fatal("expected non-empty metadata set")
}
// Verify specific labels/types are present
if _, ok := set[0x19ef64]; !ok {
    t.Error("expected label 0x19ef64 in metadata")
}

This aids debugging and serves as documentation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f01d63 and 8194f2a.

📒 Files selected for processing (10)
  • ledger/allegra/allegra.go (4 hunks)
  • ledger/alonzo/alonzo.go (4 hunks)
  • ledger/babbage/babbage.go (4 hunks)
  • ledger/byron/byron.go (2 hunks)
  • ledger/common/metadata.go (1 hunks)
  • ledger/common/metadata_test.go (1 hunks)
  • ledger/common/tx.go (1 hunks)
  • ledger/conway/conway.go (4 hunks)
  • ledger/mary/mary.go (4 hunks)
  • ledger/shelley/shelley.go (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-04T15:54:22.683Z
Learnt from: wolf31o2
Repo: blinklabs-io/gouroboros PR: 1093
File: ledger/mary/pparams.go:143-150
Timestamp: 2025-11-04T15:54:22.683Z
Learning: In the blinklabs-io/gouroboros repository, the design goal for CBOR round-trip tests is to achieve byte-identical encoding WITHOUT using stored CBOR (cbor.DecodeStoreCbor). Instead, the approach uses proper field types (pointers for optional fields) and relies on the cbor package's deterministic encoding (SortCoreDeterministic) to ensure reproducible output. The stored CBOR pattern should not be suggested as a solution for round-trip fidelity in this codebase.

Applied to files:

  • ledger/common/tx.go
  • ledger/common/metadata.go
  • ledger/common/metadata_test.go
🧬 Code graph analysis (10)
ledger/common/tx.go (1)
ledger/common/metadata.go (1)
  • TransactionMetadatum (40-43)
ledger/byron/byron.go (2)
ledger/common/metadata.go (1)
  • TransactionMetadatum (40-43)
ledger/byron.go (1)
  • ByronTransaction (28-28)
ledger/babbage/babbage.go (1)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/shelley/shelley.go (1)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/alonzo/alonzo.go (1)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/mary/mary.go (1)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/conway/conway.go (2)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/conway.go (1)
  • ConwayTransaction (26-26)
ledger/common/metadata.go (4)
cbor/decode.go (1)
  • Decode (29-44)
cbor/cbor.go (1)
  • RawMessage (36-36)
cbor/bytestring.go (2)
  • ByteString (27-29)
  • NewByteString (31-34)
cbor/encode.go (1)
  • Encode (27-40)
ledger/common/metadata_test.go (2)
ledger/common/metadata.go (1)
  • TransactionMetadataSet (38-38)
cbor/decode.go (1)
  • Decode (29-44)
ledger/allegra/allegra.go (1)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
🪛 GitHub Actions: go-test
ledger/alonzo/alonzo.go

[error] 106-106: cannot use b.TransactionMetadataSet (variable of map type "github.com/blinklabs-io/gouroboros/ledger/common".TransactionMetadataSet) as map[uint]*"github.com/blinklabs-io/gouroboros/cbor".LazyValue value in struct literal

🪛 GitHub Actions: golangci-lint
ledger/alonzo/alonzo.go

[error] 106-106: cannot use b.TransactionMetadataSet (type map["github.com/blinklabs-io/gouroboros/ledger/common".TransactionMetadataSet) as map[uint]*"github.com/blinklabs-io/gouroboros/cbor".LazyValue value in struct literal (typecheck)

🪛 GitHub Actions: nilaway
ledger/alonzo/alonzo.go

[error] 106-106: cannot use b.TransactionMetadataSet (type map["github.com/blinklabs-io/gouroboros/ledger/common".TransactionMetadataSet) as map[uint]*"github.com/blinklabs-io/gouroboros/cbor".LazyValue in struct literal. Lint/compile error encountered during 'nilaway ./...' step.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (11)
ledger/common/tx.go (1)

31-31: Clean interface update to support structured metadata.

The return type change from *cbor.LazyValue to TransactionMetadatum provides a typed API for transaction metadata, replacing the raw CBOR approach. This is consistent with implementations across all era modules in this PR.

ledger/byron/byron.go (2)

145-145: LGTM! Field type updated consistently.

The change from *cbor.LazyValue to common.TransactionMetadatum aligns with the new typed metadata model.


278-280: LGTM! Method signature matches interface.

The Metadata() implementation correctly returns the TransactionMetadatum interface value.

ledger/conway/conway.go (4)

58-58: LGTM! Block-level metadata set correctly typed.

The change from map[uint]*cbor.LazyValue to common.TransactionMetadataSet provides structured metadata handling at the block level.


521-521: LGTM! Transaction metadata field updated correctly.

The field now uses the typed TransactionMetadatum interface instead of raw CBOR.


635-637: LGTM! Method signature matches interface.

The Metadata() implementation correctly returns TransactionMetadatum.


684-712: LGTM! CBOR encoding correctly delegates to metadata's marshaler.

The change from wrapping metadata in cbor.RawMessage(t.TxMetadata.Cbor()) to appending t.TxMetadata directly is correct. This delegates encoding to the metadata type's MarshalCBOR implementation, enabling proper round-trip encoding. The consistent append behavior (always including the field, even if nil) aligns with CBOR array structure requirements.

ledger/babbage/babbage.go (4)

58-58: LGTM! Block-level metadata set correctly typed.

Consistent with the Conway changes, this provides structured metadata handling at the block level.


736-736: LGTM! Transaction metadata field updated correctly.

The field type change is consistent with the new metadata model.


850-852: LGTM! Method signature matches interface.

The Metadata() implementation is correct and consistent across eras.


899-927: LGTM! CBOR encoding pattern consistent with Conway.

The encoding correctly delegates to the metadata's MarshalCBOR implementation and consistently includes the metadata field (even if nil) in the transaction array structure.

Signed-off-by: Akhil Repala <arepala@blinklabs.io>
Signed-off-by: Akhil Repala <arepala@blinklabs.io>
Signed-off-by: Akhil Repala <arepala@blinklabs.io>
Signed-off-by: Akhil Repala <arepala@blinklabs.io>
Signed-off-by: Akhil Repala <arepala@blinklabs.io>
Signed-off-by: Akhil Repala <arepala@blinklabs.io>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
ledger/common/metadata.go (3)

45-45: Fix formatting and address the int64 range limitation.

The pipeline reports a gofumpt formatting issue on this line. Please run gofumpt -w to fix it.

Additionally, as noted in previous reviews, MetaInt.Value int64 cannot represent the full range of CBOR integers allowed by the metadata CDDL. This limitation causes decodeMapUint (line 147-149) to explicitly reject any map key exceeding math.MaxInt64, which will fail to decode valid on-chain metadata. Consider using *big.Int or at least supporting the full uint64 range to avoid rejecting legitimate metadata.


124-129: Do not coerce unsupported CBOR types into text—reject them explicitly.

As noted in previous reviews, this code decodes CBOR major types 6 (tags) and 7 (floats/simples) and converts them to text via fmt.Sprintf. The metadata CDDL does not permit these types, so this silently accepts invalid metadata and misrepresents values like floats, booleans, and nulls as strings.

Instead, return an explicit error when encountering these major types to reject non-conformant metadata.

Apply this diff:

-	case cborTypeTag, cborTypeFloatSim:
-		var x any
-		if _, err := cbor.Decode(b, &x); err != nil {
-			return nil, err
-		}
-		return MetaText{Value: fmt.Sprintf("%v", x)}, nil
+	case cborTypeTag, cborTypeFloatSim:
+		return nil, fmt.Errorf("unsupported CBOR major type %#x in metadatum (tags/floats/simples not allowed)", b[0]&cborTypeMask)

244-310: Mixed-key maps are dropped, causing data loss.

As noted in previous reviews, when a metadata map contains keys of varying types (which the spec allows), this function falls through all three homogeneous-key checks and returns nil at line 305. This causes MarshalCBOR to encode the value as CBOR null, permanently losing the map data.

To preserve such maps, detect the mixed-key case and encode as an array of key-value pairs instead of returning nil.

Apply this diff to preserve mixed-key maps:

 		if allBytes {
 			mm := make(map[cbor.ByteString]any, len(t.Pairs))
 			for _, p := range t.Pairs {
 				bs := p.Key.(MetaBytes).Value
 				key := cbor.NewByteString(bs)
 				mm[key] = metadatumToInterface(p.Value)
 			}
 			return mm
 		}
-		return nil
+		// Mixed-key map: encode as array of [key, value] pairs
+		out := make([]any, len(t.Pairs))
+		for i, p := range t.Pairs {
+			out[i] = []any{metadatumToInterface(p.Key), metadatumToInterface(p.Value)}
+		}
+		return out
 
 	default:
 		return nil
 	}
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8194f2a and 8768206.

📒 Files selected for processing (11)
  • ledger/allegra/allegra.go (4 hunks)
  • ledger/allegra/block_test.go (1 hunks)
  • ledger/alonzo/alonzo.go (5 hunks)
  • ledger/babbage/babbage.go (4 hunks)
  • ledger/byron/byron.go (2 hunks)
  • ledger/common/metadata.go (1 hunks)
  • ledger/common/metadata_test.go (1 hunks)
  • ledger/common/tx.go (1 hunks)
  • ledger/conway/conway.go (4 hunks)
  • ledger/mary/mary.go (4 hunks)
  • ledger/shelley/shelley.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • ledger/allegra/block_test.go
  • ledger/common/metadata_test.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: wolf31o2
Repo: blinklabs-io/gouroboros PR: 1093
File: ledger/mary/pparams.go:143-150
Timestamp: 2025-11-04T15:54:22.683Z
Learning: In the blinklabs-io/gouroboros repository, the design goal for CBOR round-trip tests is to achieve byte-identical encoding WITHOUT using stored CBOR (cbor.DecodeStoreCbor). Instead, the approach uses proper field types (pointers for optional fields) and relies on the cbor package's deterministic encoding (SortCoreDeterministic) to ensure reproducible output. The stored CBOR pattern should not be suggested as a solution for round-trip fidelity in this codebase.
📚 Learning: 2025-11-04T15:54:22.683Z
Learnt from: wolf31o2
Repo: blinklabs-io/gouroboros PR: 1093
File: ledger/mary/pparams.go:143-150
Timestamp: 2025-11-04T15:54:22.683Z
Learning: In the blinklabs-io/gouroboros repository, the design goal for CBOR round-trip tests is to achieve byte-identical encoding WITHOUT using stored CBOR (cbor.DecodeStoreCbor). Instead, the approach uses proper field types (pointers for optional fields) and relies on the cbor package's deterministic encoding (SortCoreDeterministic) to ensure reproducible output. The stored CBOR pattern should not be suggested as a solution for round-trip fidelity in this codebase.

Applied to files:

  • ledger/common/tx.go
  • ledger/common/metadata.go
🧬 Code graph analysis (9)
ledger/conway/conway.go (2)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/conway.go (1)
  • ConwayTransaction (26-26)
ledger/common/tx.go (1)
ledger/common/metadata.go (1)
  • TransactionMetadatum (40-43)
ledger/mary/mary.go (2)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/mary.go (1)
  • MaryTransaction (26-26)
ledger/babbage/babbage.go (2)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/babbage.go (1)
  • BabbageTransaction (26-26)
ledger/byron/byron.go (2)
ledger/common/metadata.go (1)
  • TransactionMetadatum (40-43)
ledger/byron.go (1)
  • ByronTransaction (28-28)
ledger/alonzo/alonzo.go (1)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/allegra/allegra.go (2)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/allegra.go (1)
  • AllegraTransaction (25-25)
ledger/shelley/shelley.go (2)
ledger/common/metadata.go (2)
  • TransactionMetadataSet (38-38)
  • TransactionMetadatum (40-43)
ledger/shelley.go (1)
  • ShelleyTransaction (26-26)
ledger/common/metadata.go (4)
cbor/decode.go (1)
  • Decode (29-44)
cbor/cbor.go (1)
  • RawMessage (36-36)
cbor/bytestring.go (2)
  • ByteString (27-29)
  • NewByteString (31-34)
cbor/encode.go (1)
  • Encode (27-40)
🪛 GitHub Actions: golangci-lint
ledger/common/metadata.go

[error] 45-45: golangci-lint detected gofumpt formatting issues. File is not properly formatted (gofumpt).

🪛 GitHub Check: lint
ledger/common/metadata.go

[failure] 192-192:
unnecessary leading newline (whitespace)


[failure] 174-174:
error is not nil (line 173) but it returns nil (nilerr)


[failure] 158-158:
error is not nil (line 157) but it returns nil (nilerr)


[failure] 139-139:
error is not nil (line 138) but it returns nil (nilerr)


[failure] 45-45:
File is not properly formatted (gofumpt)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (13)
ledger/common/tx.go (1)

31-31: LGTM! Clean interface update for typed metadata.

The signature change from *cbor.LazyValue to TransactionMetadatum provides a strongly-typed interface for transaction metadata, aligning with the new common metadata model. All implementations across Byron, Shelley, Allegra, Mary, Alonzo, Babbage, and Conway have been consistently updated.

ledger/mary/mary.go (3)

55-55: LGTM! Consistent metadata type update.

The field type change from map[uint]*cbor.LazyValue to common.TransactionMetadataSet aligns with the repository-wide metadata refactor and provides proper typing for transaction metadata sets.


271-271: LGTM! Metadata field and accessor updated consistently.

Both the field type and method signature are correctly updated to use common.TransactionMetadatum, maintaining consistency with the interface change in ledger/common/tx.go.

Also applies to: 385-387


434-435: LGTM! CBOR encoding correctly uses typed metadata.

The encoding now appends t.TxMetadata directly instead of cbor.RawMessage(t.TxMetadata.Cbor()), which is correct since TransactionMetadatum types implement cbor.Marshaler. The nil check ensures proper handling when metadata is absent.

ledger/byron/byron.go (1)

145-145: LGTM! Byron transaction metadata updated consistently.

Both the Attributes field and Metadata() method are correctly updated to use common.TransactionMetadatum, maintaining consistency with other era implementations.

Also applies to: 278-280

ledger/alonzo/alonzo.go (1)

58-58: LGTM! Alonzo metadata migration is complete and consistent.

All metadata-related changes (block-level set, transaction field, accessor method, and CBOR encoding) are correctly updated to use the new typed metadata model. The encoding path properly appends metadata directly when present.

Also applies to: 636-636, 750-752, 809-810

ledger/conway/conway.go (1)

58-58: LGTM! Conway metadata changes are consistent and correct.

All aspects of the metadata refactor are properly implemented: block-level TransactionMetadataSet, transaction-level TransactionMetadatum field, updated accessor method, and direct CBOR encoding when metadata is present.

Also applies to: 521-521, 635-637, 701-702

ledger/babbage/babbage.go (1)

58-58: LGTM! Babbage metadata refactor is complete.

The systematic update of metadata types and encoding logic is correctly applied across the block, transaction, accessor, and CBOR encoding paths.

Also applies to: 791-791, 905-907, 971-972

ledger/shelley/shelley.go (1)

56-56: LGTM! Shelley metadata migration is consistent with other eras.

All metadata-related changes align with the repository-wide refactor: typed metadata set at block level, typed metadata field at transaction level, updated accessor signature, and direct CBOR encoding.

Also applies to: 543-543, 653-655, 711-712

ledger/allegra/allegra.go (1)

52-52: LGTM! Allegra completes the cross-era metadata refactor.

The changes are consistent with all other era implementations, successfully migrating from *cbor.LazyValue to the strongly-typed common.TransactionMetadatum model. The nil handling in CBOR encoding is correct.

Also applies to: 242-242, 352-354, 413-414

ledger/common/metadata.go (3)

25-36: LGTM!

The CBOR major type constants are correctly defined and aligned with RFC 8949.


59-69: LGTM!

The interface marker methods and TypeName implementations are straightforward and correct.


233-242: LGTM!

The MarshalCBOR implementation correctly handles both nil and populated metadata sets, delegating to metadatumToInterface for type conversion.

Signed-off-by: Akhil Repala <arepala@blinklabs.io>
Signed-off-by: Akhil Repala <arepala@blinklabs.io>
@arepala-uml arepala-uml requested a review from agaffney November 17, 2025 08:29
@wolf31o2
Copy link
Member

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 17, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

func (t *ByronTransaction) Metadata() *cbor.LazyValue {
return t.Attributes
func (t *ByronTransaction) Metadata() common.TransactionMetadatum {
return nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely not correct. This probably needs its own Byron-specific type, since it'll be different from things afterwards.

…metdata part

Signed-off-by: Akhil Repala <arepala@blinklabs.io>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
ledger/common/metadata.go (1)

318-334: Mixed-key maps are lost during encoding.

When a MetaMap has keys of varying types (allowed by the CDDL spec), line 334 returns nil, causing MarshalCBOR to encode it as CBOR null and permanently lose the map data. This is the issue flagged in the previous review.

Based on the past review suggestion, preserve mixed-key maps by encoding them as an array of key/value pairs:

+    // Mixed key types: encode as array of pairs
+    pairArray := make([]any, 0, len(t.Pairs))
+    for _, p := range t.Pairs {
+        pairArray = append(pairArray, []any{
+            metadatumToInterface(p.Key),
+            metadatumToInterface(p.Value),
+        })
+    }
-    return nil
+    return pairArray
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb23dd3 and 482298b.

📒 Files selected for processing (2)
  • ledger/allegra/block_test.go (0 hunks)
  • ledger/common/metadata.go (1 hunks)
💤 Files with no reviewable changes (1)
  • ledger/allegra/block_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-04T15:54:22.683Z
Learnt from: wolf31o2
Repo: blinklabs-io/gouroboros PR: 1093
File: ledger/mary/pparams.go:143-150
Timestamp: 2025-11-04T15:54:22.683Z
Learning: In the blinklabs-io/gouroboros repository, the design goal for CBOR round-trip tests is to achieve byte-identical encoding WITHOUT using stored CBOR (cbor.DecodeStoreCbor). Instead, the approach uses proper field types (pointers for optional fields) and relies on the cbor package's deterministic encoding (SortCoreDeterministic) to ensure reproducible output. The stored CBOR pattern should not be suggested as a solution for round-trip fidelity in this codebase.

Applied to files:

  • ledger/common/metadata.go
🧬 Code graph analysis (1)
ledger/common/metadata.go (6)
cbor/value.go (1)
  • Value (29-33)
cbor/encode.go (1)
  • Encode (27-40)
cbor/decode.go (1)
  • Decode (29-44)
cbor/cbor.go (1)
  • RawMessage (36-36)
cbor/bytestring.go (2)
  • ByteString (27-29)
  • NewByteString (31-34)
cbor/tags.go (1)
  • Set (177-177)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🔇 Additional comments (6)
ledger/common/metadata.go (6)

45-45: Correct use of *big.Int for arbitrary precision.

The use of *big.Int properly addresses the previous concern about integer range limitations. On-chain metadata can contain arbitrary-sized integers, and this implementation now handles them correctly.


80-96: LGTM: Clean helper for hashable integer keys.

The metaIntKey approach using string representation is a clever solution to make big.Int values hashable for use as map keys while preserving arbitrary precision.


151-155: Correct rejection of unsupported CBOR types.

This properly returns an error for tags and floats/simpleValues instead of coercing them into text, addressing the previous concern about misrepresenting invalid metadata.


162-213: LGTM: Map decoders handle multiple key types correctly.

The three specialized decoders (decodeMapUint, decodeMapText, decodeMapBytes) properly handle different CBOR map key types and include good defensive practices like the byte copy at line 208.


215-253: LGTM: Flexible unmarshaling supports both map and array forms.

The function correctly tries both encodings and handles null-skipping for array form, providing good flexibility for CBOR decoding.


255-264: Clean marshaling implementation.

The MarshalCBOR function is well-structured. Note that its correctness depends on metadatumToInterface properly handling all cases (see related comments on that function).

…metdata part

Signed-off-by: Akhil Repala <arepala@blinklabs.io>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 11 files

Prompt for AI agents (all 6 issues)

Understand the root cause of the following 6 issues and fix them.


<file name="ledger/allegra/allegra.go">

<violation number="1" location="ledger/allegra/allegra.go:414">
Metadata is appended as a raw struct, so the generated transaction CBOR will encode Go struct fields instead of the Cardano metadata representation.</violation>
</file>

<file name="ledger/common/metadata.go">

<violation number="1" location="ledger/common/metadata.go:163">
Metadata maps that use negative integer keys cannot be decoded because `decodeMapUint` only attempts `map[uint]` and therefore rejects valid CBOR with signed keys.</violation>

<violation number="2" location="ledger/common/metadata.go:261">
Marshaling silently replaces `MetaMap` values with `null` whenever the map contains mixed key types, so valid metadata is dropped without reporting an error.</violation>
</file>

<file name="ledger/mary/mary.go">

<violation number="1" location="ledger/mary/mary.go:435">
Metadata is appended to the transaction CBOR as a Meta* struct, so it serializes as a Go struct (with `Value` fields) instead of the proper metadata CBOR, breaking Mary transaction encoding when metadata exists.</violation>
</file>

<file name="ledger/conway/conway.go">

<violation number="1" location="ledger/conway/conway.go:702">
Metadata is appended as a raw struct, so cbor.Encode emits the struct fields (`Value`, `Items`, etc.) instead of the actual metadata value, breaking transaction serialization whenever metadata is present.</violation>
</file>

<file name="ledger/shelley/shelley.go">

<violation number="1" location="ledger/shelley/shelley.go:712">
Tx metadata is now appended to the CBOR array as a raw TransactionMetadatum struct, which the CBOR encoder turns into a Go-struct representation rather than valid metadata, breaking serialization whenever metadata is present.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

}
if t.TxMetadata != nil {
tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
tmpObj = append(tmpObj, t.TxMetadata)
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metadata is appended as a raw struct, so the generated transaction CBOR will encode Go struct fields instead of the Cardano metadata representation.

Prompt for AI agents
Address the following comment on ledger/allegra/allegra.go at line 414:

<comment>Metadata is appended as a raw struct, so the generated transaction CBOR will encode Go struct fields instead of the Cardano metadata representation.</comment>

<file context>
@@ -411,7 +411,7 @@ func (t *AllegraTransaction) Cbor() []byte {
 	}
 	if t.TxMetadata != nil {
-		tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
+		tmpObj = append(tmpObj, t.TxMetadata)
 	} else {
 		tmpObj = append(tmpObj, nil)
</file context>
Fix with Cubic

}
tmpMap := make(map[uint]any, len(s))
for k, v := range s {
tmpMap[k] = metadatumToInterface(v)
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marshaling silently replaces MetaMap values with null whenever the map contains mixed key types, so valid metadata is dropped without reporting an error.

Prompt for AI agents
Address the following comment on ledger/common/metadata.go at line 261:

<comment>Marshaling silently replaces `MetaMap` values with `null` whenever the map contains mixed key types, so valid metadata is dropped without reporting an error.</comment>

<file context>
@@ -0,0 +1,339 @@
+	}
+	tmpMap := make(map[uint]any, len(s))
+	for k, v := range s {
+		tmpMap[k] = metadatumToInterface(v)
+	}
+	return cbor.Encode(&amp;tmpMap)
</file context>
Fix with Cubic

}

func decodeMapUint(b []byte) (TransactionMetadatum, bool, error) {
var m map[uint]cbor.RawMessage
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metadata maps that use negative integer keys cannot be decoded because decodeMapUint only attempts map[uint] and therefore rejects valid CBOR with signed keys.

Prompt for AI agents
Address the following comment on ledger/common/metadata.go at line 163:

<comment>Metadata maps that use negative integer keys cannot be decoded because `decodeMapUint` only attempts `map[uint]` and therefore rejects valid CBOR with signed keys.</comment>

<file context>
@@ -0,0 +1,339 @@
+}
+
+func decodeMapUint(b []byte) (TransactionMetadatum, bool, error) {
+	var m map[uint]cbor.RawMessage
+	if _, err := cbor.Decode(b, &amp;m); err != nil {
+		return nil, false, nil //nolint:nilerr // not this shape
</file context>
Fix with Cubic

}
if t.TxMetadata != nil {
tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
tmpObj = append(tmpObj, t.TxMetadata)
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metadata is appended to the transaction CBOR as a Meta* struct, so it serializes as a Go struct (with Value fields) instead of the proper metadata CBOR, breaking Mary transaction encoding when metadata exists.

Prompt for AI agents
Address the following comment on ledger/mary/mary.go at line 435:

<comment>Metadata is appended to the transaction CBOR as a Meta* struct, so it serializes as a Go struct (with `Value` fields) instead of the proper metadata CBOR, breaking Mary transaction encoding when metadata exists.</comment>

<file context>
@@ -432,7 +432,7 @@ func (t *MaryTransaction) Cbor() []byte {
 	}
 	if t.TxMetadata != nil {
-		tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
+		tmpObj = append(tmpObj, t.TxMetadata)
 	} else {
 		tmpObj = append(tmpObj, nil)
</file context>
Fix with Cubic

}
if t.TxMetadata != nil {
tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
tmpObj = append(tmpObj, t.TxMetadata)
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metadata is appended as a raw struct, so cbor.Encode emits the struct fields (Value, Items, etc.) instead of the actual metadata value, breaking transaction serialization whenever metadata is present.

Prompt for AI agents
Address the following comment on ledger/conway/conway.go at line 702:

<comment>Metadata is appended as a raw struct, so cbor.Encode emits the struct fields (`Value`, `Items`, etc.) instead of the actual metadata value, breaking transaction serialization whenever metadata is present.</comment>

<file context>
@@ -699,7 +699,7 @@ func (t *ConwayTransaction) Cbor() []byte {
 	}
 	if t.TxMetadata != nil {
-		tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
+		tmpObj = append(tmpObj, t.TxMetadata)
 	} else {
 		tmpObj = append(tmpObj, nil)
</file context>
Fix with Cubic

}
if t.TxMetadata != nil {
tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
tmpObj = append(tmpObj, t.TxMetadata)
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tx metadata is now appended to the CBOR array as a raw TransactionMetadatum struct, which the CBOR encoder turns into a Go-struct representation rather than valid metadata, breaking serialization whenever metadata is present.

Prompt for AI agents
Address the following comment on ledger/shelley/shelley.go at line 712:

<comment>Tx metadata is now appended to the CBOR array as a raw TransactionMetadatum struct, which the CBOR encoder turns into a Go-struct representation rather than valid metadata, breaking serialization whenever metadata is present.</comment>

<file context>
@@ -709,7 +709,7 @@ func (t *ShelleyTransaction) Cbor() []byte {
 	}
 	if t.TxMetadata != nil {
-		tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
+		tmpObj = append(tmpObj, t.TxMetadata)
 	} else {
 		tmpObj = append(tmpObj, nil)
</file context>
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 10 files

Prompt for AI agents (all 4 issues)

Understand the root cause of the following 4 issues and fix them.


<file name="ledger/alonzo/alonzo.go">

<violation number="1" location="ledger/alonzo/alonzo.go:810">
`AlonzoTransaction.Cbor()` now appends the typed metadatum struct directly, so the CBOR encoder serializes Go struct fields instead of canonical metadata. Convert the metadata to its CBOR form (as done in TransactionMetadataSet.MarshalCBOR) before appending to tmpObj or keep using the raw CBOR bytes.</violation>
</file>

<file name="ledger/common/metadata.go">

<violation number="1" location="ledger/common/metadata.go:163">
decodeMapUint rejects metadata maps whose keys are negative integers, even though metadata allows signed int keys, so valid CBOR fails to decode.</violation>

<violation number="2" location="ledger/common/metadata.go:261">
MarshalCBOR drops any metadata map that mixes key kinds by encoding it as null instead of returning an error or preserving the pairs, causing data loss.</violation>
</file>

<file name="ledger/allegra/allegra.go">

<violation number="1" location="ledger/allegra/allegra.go:414">
`TransactionMetadatum` is appended to the CBOR array as a Go struct, so CBOR encoding no longer matches the metadata CDDL and produces invalid transactions whenever metadata is present.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

}
if t.TxMetadata != nil {
tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
tmpObj = append(tmpObj, t.TxMetadata)
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AlonzoTransaction.Cbor() now appends the typed metadatum struct directly, so the CBOR encoder serializes Go struct fields instead of canonical metadata. Convert the metadata to its CBOR form (as done in TransactionMetadataSet.MarshalCBOR) before appending to tmpObj or keep using the raw CBOR bytes.

Prompt for AI agents
Address the following comment on ledger/alonzo/alonzo.go at line 810:

<comment>`AlonzoTransaction.Cbor()` now appends the typed metadatum struct directly, so the CBOR encoder serializes Go struct fields instead of canonical metadata. Convert the metadata to its CBOR form (as done in TransactionMetadataSet.MarshalCBOR) before appending to tmpObj or keep using the raw CBOR bytes.</comment>

<file context>
@@ -807,7 +807,7 @@ func (t *AlonzoTransaction) Cbor() []byte {
 	}
 	if t.TxMetadata != nil {
-		tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
+		tmpObj = append(tmpObj, t.TxMetadata)
 	} else {
 		tmpObj = append(tmpObj, nil)
</file context>
Fix with Cubic

}
tmpMap := make(map[uint]any, len(s))
for k, v := range s {
tmpMap[k] = metadatumToInterface(v)
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MarshalCBOR drops any metadata map that mixes key kinds by encoding it as null instead of returning an error or preserving the pairs, causing data loss.

Prompt for AI agents
Address the following comment on ledger/common/metadata.go at line 261:

<comment>MarshalCBOR drops any metadata map that mixes key kinds by encoding it as null instead of returning an error or preserving the pairs, causing data loss.</comment>

<file context>
@@ -0,0 +1,340 @@
+	}
+	tmpMap := make(map[uint]any, len(s))
+	for k, v := range s {
+		tmpMap[k] = metadatumToInterface(v)
+	}
+	return cbor.Encode(&amp;tmpMap)
</file context>
Fix with Cubic

}

func decodeMapUint(b []byte) (TransactionMetadatum, bool, error) {
var m map[uint]cbor.RawMessage
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decodeMapUint rejects metadata maps whose keys are negative integers, even though metadata allows signed int keys, so valid CBOR fails to decode.

Prompt for AI agents
Address the following comment on ledger/common/metadata.go at line 163:

<comment>decodeMapUint rejects metadata maps whose keys are negative integers, even though metadata allows signed int keys, so valid CBOR fails to decode.</comment>

<file context>
@@ -0,0 +1,340 @@
+}
+
+func decodeMapUint(b []byte) (TransactionMetadatum, bool, error) {
+	var m map[uint]cbor.RawMessage
+	if _, err := cbor.Decode(b, &amp;m); err != nil {
+		return nil, false, nil //nolint:nilerr // not this shape
</file context>
Fix with Cubic

}
if t.TxMetadata != nil {
tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
tmpObj = append(tmpObj, t.TxMetadata)
Copy link

@cubic-dev-ai cubic-dev-ai bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TransactionMetadatum is appended to the CBOR array as a Go struct, so CBOR encoding no longer matches the metadata CDDL and produces invalid transactions whenever metadata is present.

Prompt for AI agents
Address the following comment on ledger/allegra/allegra.go at line 414:

<comment>`TransactionMetadatum` is appended to the CBOR array as a Go struct, so CBOR encoding no longer matches the metadata CDDL and produces invalid transactions whenever metadata is present.</comment>

<file context>
@@ -411,7 +411,7 @@ func (t *AllegraTransaction) Cbor() []byte {
 	}
 	if t.TxMetadata != nil {
-		tmpObj = append(tmpObj, cbor.RawMessage(t.TxMetadata.Cbor()))
+		tmpObj = append(tmpObj, t.TxMetadata)
 	} else {
 		tmpObj = append(tmpObj, nil)
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support proper encoding/decoding of TX metadata

4 participants